<?php

namespace app\common\service;

/*
 * 有道云-智能语音合成（text-to-speech，TTS）
 * */

use app\common\service\word\WordUtilsService;
use think\exception\PDOException;
use think\facade\Log;

class TextToSpeechService
{
    protected $app_key;
    protected $app_secret;

    public function __construct()
    {
        $this->app_key = env('youdao.app_key');
        $this->app_secret = env('youdao.app_secret');
    }

    /*
     * 创建请求
     * $voiceName youyating=有雅婷   女 英文\美式
     * $voiceName youxiaozhi=有小智    男   中文
     * $type dub=配音目录 word=单词目录 relate=关联词目录
     * */
    public function create_request($text, $voiceName = 'youmeimei')
    {
        $maxRetries = 3;
        $retryDelay = 500; // 毫秒

        for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
            try {
                Log::info("尝试有道语音合成 (第{$attempt}次) - 单词: {$text}");

                // 1. 准备请求参数
                $params = [
                    'q' => $text,
                    'voiceName' => $voiceName,
                    'format' => 'mp3',
                    'speed' => 1,
                ];
                $params = $this->add_auth_params($params, $this->app_key, $this->app_secret);

                // 2. 执行API调用（带详细诊断）
                $response = $this->do_call_with_diagnostics(
                    'https://openapi.youdao.com/ttsapi',
                    'post',
                    [],
                    $params,
                    'audio'
                );

                // 3. 处理成功响应-将音频文件保存到本地
                $file_path = WordUtilsService::getVocalOssKey($text, 'mp3');
                // 4. 上传到COS
                $UploadService = new UploadService();
                $upload_result = $UploadService->uploadFileSimple($response, $file_path);
                return $upload_result;
            } catch (\Exception $e) {

                // 特殊处理业务错误（不重试）
                if (strpos($e->getMessage(), 'API业务错误') !== false) {
                    Log::error("参数错误终止重试: " . $e->getMessage());
                    throw $e; // 直接抛出不重试
                }

                Log::error("尝试 {$attempt} 失败: " . $e->getMessage());

                // 最后一次尝试后仍失败
                if ($attempt === $maxRetries) {
                    Log::error("语音合成最终失败: {$text}");
                    throw new \Exception("语音合成失败: " . $e->getMessage());
                }

                // 指数退避等待
                usleep($retryDelay * 1000);
                $retryDelay *= 2;
            }
        }
    }

    private function do_call_with_diagnostics($url, $method, $header, $param, $expectContentType)
    {
        $ch = curl_init();

        // 详细CURL配置
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => '',
            CURLOPT_MAXREDIRS => 3,
            CURLOPT_TIMEOUT => 10,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
        ]);

        if ($method === 'post') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($param));
        }

        // 执行请求
        $response = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

        // ==== 关键修复：优先处理JSON错误响应 ====
        if ($httpCode === 200 && strpos($contentType, 'application/json') !== false) {
            $errorData = json_decode($response, true);
            $errorCode = $errorData['errorCode'] ?? 'unknown';
            $errorMsg = $errorData['message'] ?? $response;
            throw new \Exception("API业务错误 {$errorCode}: {$errorMsg}");
        }

        // 诊断日志
        Log::debug("API请求诊断:
            URL: {$url}
            Method: {$method}
            Params: " . json_encode($param) . "
            HTTP Code: {$httpCode}
            Content-Type: {$contentType}
            cURL Error: {$errno} - {$error}
            Response Length: " . strlen($response ?? '')
        );

        if ($errno) {
            throw new \Exception("cURL错误 {$errno}: {$error}");
        }

        if ($httpCode !== 200) {
            throw new \Exception("HTTP错误 {$httpCode}");
        }

        if (strpos($contentType, $expectContentType) === false) {
            $sample = substr($response, 0, 200);
            throw new \Exception("内容类型不匹配: 期望 {$expectContentType}, 实际 {$contentType}. 响应示例: {$sample}");
        }

        return $response;
    }

    public function add_auth_params($param, $appKey, $appSecret)
    {
        if (array_key_exists('q', $param)) {
            $q = $param['q'];
        } else {
            $q = $param['img'];
        }
        $salt = $this->create_uuid();
        $curtime = strtotime("now");
        $sign = $this->calculate_sign($appKey, $appSecret, $q, $salt, $curtime);
        $param['appKey'] = $appKey;
        $param['salt'] = $salt;
        $param["curtime"] = $curtime;
        $param['signType'] = 'v3';
        $param['sign'] = $sign;
        return $param;
    }

    public function create_uuid()
    {
        $str = md5(uniqid(mt_rand(), true));
        $uuid = substr($str, 0, 8) . '-';
        $uuid .= substr($str, 8, 4) . '-';
        $uuid .= substr($str, 12, 4) . '-';
        $uuid .= substr($str, 16, 4) . '-';
        $uuid .= substr($str, 20, 12);
        return $uuid;
    }

    public function calculate_sign($appKey, $appSecret, $q, $salt, $curtime)
    {
        $strSrc = $appKey . $this->get_input($q) . $salt . $curtime . $appSecret;
        return hash("sha256", $strSrc);
    }

    public function get_input($q)
    {
        if (empty($q)) {
            return null;
        }
        $len = mb_strlen($q, 'utf-8');
        return $len <= 20 ? $q : (mb_substr($q, 0, 10) . $len . mb_substr($q, $len - 10, $len));
    }

}
